//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "ddk.h"

#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <stdlib.h>
#include <tfssys.h>
#include <misc.h>
#include <fat.h>
#include "string.h"

/* DIRTY() Mark dirtymap at given position */
#define DIRTY(idx) (dirtymap[(idx * sizeof(fat16_t)) / p_Sharedata->secsz] = 1)

/*
 * fat16_loadfat()
 *    Load FAT handling
 *
 * Read in the FAT, convert if needed.
 */
int fat16::
fat16_loadfat(class fatfs_sharedata *p_Sharedata)
{
    if (fat == 0 || dirtymap == 0) {
        return -1;
    }
    /*
     * clear FAT dirtymap
     */
    bzero(dirtymap, dirtymapsize);
    /*
     * Seek to FAT table on disk, read into buffer
     */
    ECode ec = ReadDevice(
                p_Sharedata->pBlkDev,
                (p_Sharedata->bootb.BPB_ResvdSecCnt + p_Sharedata->bpb_base)
                    * p_Sharedata->secsz,
                fatlen,
                (PByte)fat,
                NULL, NULL);
    if (FAILED(ec)) {
        DPRINTF(("ERROR: Failed to Read from the offset:%d", \
            p_Sharedata->bootb.BPB_ResvdSecCnt * p_Sharedata->secsz));
        return -1;
    }

    return 0;
}

/*
 * fat16_init()
 *    Initialize FAT handling
 *
 * Read in the FAT, convert if needed.
 */
int fat16::
fat16_init(class fatfs_sharedata *pSharedata)
{

    ASSERT_DEBUG(pSharedata!=NULL, "fat_setlen: bad clust");
    p_Sharedata = pSharedata;
    p_Sharedata->AddRef();
    /*
     * Get map of dirty sectors in FAT
     */
    dirtymapsize =
        roundup(p_Sharedata->nclust * sizeof(fat16_t), p_Sharedata->secsz) / p_Sharedata->secsz;
    dirtymap = (UInt8 *)malloc(dirtymapsize);
    ASSERT_DEBUG(dirtymap, "fat16_init: dirtymap");
    /*
     * Get memory for FAT table
     */
    fatlen = p_Sharedata->bootb.BPB_FATSz16 * p_Sharedata->secsz;
    fat = (fat16_t *)malloc(fatlen);

    return fat16_loadfat(p_Sharedata);
}

/*
 * fat16_setlen()
 *    Twiddle FAT allocation to match the indicated length
 *
 * Returns 0 if it could be done; 1 if it failed.
 */
int fat16::
fat16_setlen(struct clust *c, UInt32 newclust)
{
    UInt32 clust_cnt, x;
    UInt32 cl, *ctmp;

    /*
     * Getting smaller--free stuff at the end
     */
    if (c->c_nclust > newclust) {
        /*
         * it is very surprise to find this expression, nxt_clust may be
         * invalid
         */
        //nxt_clust = newclust;  ????
        nxt_clust = c->c_clust[newclust];

        for (x = newclust; x < c->c_nclust;++x) {
            cl = c->c_clust[x];
#ifdef DEBUG
            if ((cl > p_Sharedata->nclust + 1) || (cl < 2)) {
                UInt32 z;

                DPRINTF(("bad cluster 0x%x", cl));
                DPRINTF(("clusters in file:"));
                for (z = 0; z < c->c_nclust;++z) {
                    DPRINTF((" %x", c->c_clust[z]));
                }
                ASSERT_DEBUG(0, "fat_setlen: bad clust");
            }
#endif
            fat[cl] = 0;
            c->c_clust[x] = 0;
            DeleteBlock( p_Sharedata->pBlkDev, BOFF(cl),
                            p_Sharedata->bootb.BPB_SecPerClus);
            DIRTY(cl);
        }
        if (newclust > 0) {
            fat[cl = c->c_clust[newclust-1]] = FAT16_EOF;
            DIRTY(cl);
        }
        if (newclust < c->n_newclust)
            c->n_newclust = 0x7fffffff;    // new added
        c->c_nclust = newclust;
        p_Sharedata->fat_dirty = 1;
        return(0);
    }

    /*
     * Trying to grow.  See if we can get the blocks.  If we can't,
     * we bail out.  The allocation is done in two passes, so that
     * when we bail after running out of space there's nothing which
     * needs to be undone.  The realloc()'ed c_clust is harmless.
     */
    //ctmp = (UInt32 *)realloc(c->c_clust, newclust * sizeof(UInt32));
    ctmp = (UInt32 *)malloc(newclust * sizeof(UInt32));
    if (ctmp == 0) {
        return(1);
    }
    memcpy(ctmp, c->c_clust, c->c_nclust * sizeof(UInt32));
    if (c->c_clust) {
        free(c->c_clust);
    }
    c->c_clust = ctmp;
    cl = nxt_clust;
    clust_cnt = 0;
    for (x = c->c_nclust; x < newclust;++x) {
        /*
         * Scan for next free cluster
         */
        while ((clust_cnt++< p_Sharedata->nclust) && fat[cl]) {
            if (++cl > p_Sharedata->nclust + 1) {
                cl = 2;//BUGBUG: cluster 0, 1 is reserved, maybe cluster 2
            }
        }

        /*
         * If we didn't find one, fail
         */
        if (clust_cnt > p_Sharedata->nclust) {
            return(1);
        }

        /*
         * Tag where last search finished to save time
         */
        nxt_clust = cl + 1;
        if (nxt_clust > p_Sharedata->nclust + 1) {
            nxt_clust = 2;
        }

        /*
         * Sanity
         */
        ASSERT_DEBUG((cl >= 2) && (cl < (p_Sharedata->nclust + 2)),
            "clust_setlen: bad FAT");

        /*
         * Otherwise add it to our array.  We will flag it
         * consumed soon.
         */
        ctmp[x] = cl++;

        /*
         * clust 0, 1 are both reserved, so jump to clust 2 will be more safe
         */
        if (cl > p_Sharedata->nclust + 1) {
            cl = 2;
        }
    }

    /*
     * When we get here, the new clusters for the file extension
     * have been found and filled into the c_clust[] array.
     * We now go back and (1) flag the FAT entries consumed,
     * which also builds the cluster chain, and (2) update the
     * count of clusters for this file.
     */

    /*
     * Chain last block which already existed onto this new
     * space.
     */
    if (c->c_nclust > 0) {
        fat[cl = c->c_clust[c->c_nclust-1]] = c->c_clust[c->c_nclust];
        DIRTY(cl);
    }

    /*
     * Chain all the new clusters together
     */
    for (x = c->c_nclust; x < newclust-1;++x) {
        fat[cl = c->c_clust[x]] = c->c_clust[x+1];
        DIRTY(cl);
    }

    /*
     * Mark the EOF cluster for the last one
     */
    fat[cl = c->c_clust[newclust-1]] = FAT16_EOF;
    DIRTY(cl);

    if (c->n_newclust > newclust)
        c->n_newclust = c->c_nclust-1;    // new added
    c->c_nclust = newclust;
    p_Sharedata->fat_dirty = 1;
    return(0);
}

/*
 * fat16_alloc()
 *    Allocate a description of the given cluster chain
 */
struct clust * fat16::
fat16_alloc(struct clust *c, struct directory *d)
{
    UInt32 nclust = 1, start, x;

    /*
     * Get starting cluster from directory entry
     */
    start = d->DIR_Start;

    /*
     * Scan the chain to get its length
     */
    for (x = start; ((fat[x] < FAT16_RESERVED) && (fat[x] >= 2)); x = fat[x]) {
        ASSERT_DEBUG(x >= 2, "alloc_clust: free cluster in file");
        nclust++;
    }

    /*
     * Allocate the description array
     */
    c->c_clust = (UInt32 *)malloc(nclust * sizeof(UInt32));
    if (c->c_clust == 0) {
        return(0);
    }
    c->c_nclust = nclust;

    /*
     * Scan again, recording each cluster number
     */
    x = start;
    nclust = 0;
    do {
        c->c_clust[nclust++] = x;
        x = fat[x];
    } while ((x < FAT16_RESERVED) && (x >= 2));
    return(c);
}

/*
* fat16_sync()
*    Write a FAT16 using the dirtymap to minimize I/O
*/
void fat16::
fat16_sync(void)
{
    int x, cnt, pass, fatsize;
    off_t off;

    fatsize = (fatlen+p_Sharedata->secsz-1) >> p_Sharedata->secshift;
    /*
     * There are two copies of the FAT, so do them iteratively
     */
    for (pass = 0; pass <= 1;++pass) {
        /*
         * Calculate the offset once per pass
         */
        off = pass*(long)fatlen;

        /*
         * Walk across the dirty map, find the next dirty sector
         * of FAT information to write out.
         */
        for (x = 0; x < (int)dirtymapsize;) {
            /*
             * Not dirty.  Advance to next sector's worth.
             */
            if (!dirtymap[x]) {
                x += 1;
                continue;
            }

            /*
             * Now find runs, so we can flush adjacent sectors
             * in a single operation.
             */
            for (cnt = 1; ((x + cnt) < (int)dirtymapsize) &&
                dirtymap[x + cnt];++cnt) {
                ;
            }

            /*
             * Seek to the right place, and write the data
             */
            if (x < fatsize) {
                ECode ec = WriteDevice(
                            p_Sharedata->pBlkDev,
                            (((p_Sharedata->bootb.BPB_ResvdSecCnt + x +
                                p_Sharedata->bpb_base) << p_Sharedata->secshift) + off),
                            (cnt << p_Sharedata->secshift),
                            (PByte) & fat[(x << p_Sharedata->secshift) / sizeof(fat16_t)],
                            NULL, NULL);
                if (FAILED(ec)) {
                    DPRINTF(("ERROR: Failed to Write from the offset:%d", \
                        ((p_Sharedata->bootb.BPB_ResvdSecCnt + x) << p_Sharedata->secshift)
                        + off));
                    return;
                }
            }
            else {
                DPRINTF(("WARNING: Overflow in writting FAT16 table. offset:"
                                " %d, len: %d\n", x, cnt));
            }
            /*
             * Advance x
             */
            x += cnt;
        }
    }

    /*
     * Clear dirtymap--everything's been flushed successfully
     */
    bzero(dirtymap, dirtymapsize);
}

fat16::fat16()
{
    fat = NULL;
    dirtymap = NULL;
    dirtymapsize = 0;
    nxt_clust = 2;
    fatlen = 0;
    p_Sharedata = NULL;
}

fat16::~fat16()
{
    if (fat != NULL) {
        free(fat);
    }

    if (dirtymap != NULL) {
        free(dirtymap);
    }
    if (p_Sharedata)
        p_Sharedata->Release();
}

/*
 * Calculate the free space in disk
 */
UInt64 fat16::fat16_calcfreespace()
{
    UInt32 totalFreeClust = 0, clustEnd;
    UInt64 totalByte = 0;

    clustEnd = p_Sharedata->nclust + 1;

    for (UInt32 x = 2; x <= clustEnd;++x) {
        if (!fat[x]) {
            totalFreeClust++;
        }
    }

    totalByte = ((UInt64)totalFreeClust) * p_Sharedata->clsize;
    return totalByte;
}

#if defined(_DEBUG)
void fat16::fat16_stat(UInt32 *Fat_secs)
{
    UInt32  i,cnt = 0;
    for (i = 0; i < dirtymapsize;++i) {
        if (dirtymap[i])
            ++cnt;
    }

    *Fat_secs = cnt;
}
#endif
